#include "pila.h"
#include <stdio.h>
#include <stdlib.h>

/* ******************************************************************
 *                DEFINICION DE CONSTANTES							*
 * *****************************************************************/

#define ctam 25

/* ******************************************************************
 *                DEFINICION DE LOS TIPOS DE DATOS					*
 * *****************************************************************/

/* Se trata de una pila que contiene datos de tipo void* 
 * (punteros genéricos).*/
 
struct _pila {
    void** datos;
    size_t tamanio;
    size_t cantidad;
};

/* ******************************************************************
 *                IMPLEMENTACION DE PRIMITIVAS						*
 * *****************************************************************/
 
// Crea una pila.
// Post: devuelve una nueva pila vacía.
pila_t* pila_crear () {
	pila_t* pila = (void*) malloc(sizeof(pila_t));
	if (!pila) return NULL;
	
	pila->datos = (void**) calloc(ctam,sizeof(void**));
    if (!(pila->datos)) {
        free(pila);
        return NULL;
    }
    pila->tamanio = ctam;
	pila->cantidad = 0;
	return pila;
}

// Devuelve verdadero o falso, según si la pila tiene o no elementos apilados.
// Pre: la pila fue creada.
bool pila_esta_vacia (const pila_t *pila) {
	if (!pila) return false;
	if (pila->cantidad == 0) return true;
	return false;
}

// Redimensiona la pila y edita el campo "tamanio", devuelve falso en caso de error.
// Pre: la pila fue creada.
// Post: se redimensionó la pila.
bool redimensionar_pila (pila_t *pil, size_t tama) {
	void** datos_nuevo = (void**) realloc (pil->datos,tama*sizeof(void*));
	if (!datos_nuevo) return false;
	pil->datos = datos_nuevo; 
	pil->tamanio= tama;
	return true;
}

// Agrega un nuevo elemento a la pila. Devuelve falso en caso de error.
// Pre: la pila fue creada.
// Post: se agregó un nuevo elemento a la pila, valor es el nuevo tope.
bool pila_apilar (pila_t *pila, void* valor) {
	if (!pila) return false;
	if (pila->tamanio == pila->cantidad) {
		if (!redimensionar_pila (pila,(pila->tamanio)*3)) return false;
	}
	pila->datos[pila->cantidad] = valor;
	pila->cantidad++;
	return true;
}

// Obtiene el valor del tope de la pila. Si la pila tiene elementos,
// se devuelve el valor del tope. Si está vacía devuelve NULL.
// Pre: la pila fue creada.
// Post: se devolvió el valor del tope de la pila, cuando la pila no está
// vacía, NULL en caso contrario.
void* pila_ver_tope (const pila_t *pila) {
	if (!pila) return NULL;
	if (pila->cantidad == 0) return NULL;
	return pila->datos[(pila->cantidad)-1];
}

// Saca el elemento tope de la pila. Si la pila tiene elementos, se quita el
// tope de la pila, y se devuelve ese valor. Si la pila está vacía, devuelve 
// NULL.
// Pre: la pila fue creada.
// Post: si la pila no estaba vacía, se devuelve el valor del tope anterior 
// y la pila contiene un elemento menos.
void* pila_desapilar (pila_t *pila) {
	if (!pila) return NULL;
	if (pila->cantidad == 0) return NULL;
	
	pila->cantidad--;
	void* aux = pila->datos[pila->cantidad];
	pila->datos[pila->cantidad] = NULL;
	
	bool mucho_espacio = ((pila->cantidad) < ((pila->tamanio) / 2));
	if (((pila->cantidad) > ctam) && (mucho_espacio)) {
		redimensionar_pila (pila,(pila->tamanio)*75/100);
	}
	return aux;
}

// Destruye la pila. Si se recibe la función destruir_dato por parámetro,
// para cada uno de los elementos de la pila llama a destruir_dato.
// Pre: la pila fue creada. destruir_dato es una función capaz de destruir
// los datos de la pila, o NULL en caso de que no se la utilice.
// Post: se eliminaron todos los elementos de la pila.
void pila_destruir (pila_t *pila, void destruir_dato(void*)) {
	while (!pila_esta_vacia(pila)) {
		void *dato_aux = pila_desapilar (pila);
		if (destruir_dato) destruir_dato(dato_aux);
	}
	free (pila->datos);
	free (pila);
}
